package com.android.inputmethod.pinyin;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;

import com.android.inputmethod.pinyin.LancooComposingView.ComposingStatus;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class LancooPinyinIME {
    /**
     * TAG for debug.
     */
    static final String TAG = "LancooPinyinIME";

    /**
     * If is is true, IME will simulate key events for delete key,
     * and send the events back to the application.
     */
    private static final boolean SIMULATE_KEY_DELETE = true;

    /**
     * The current IME status.
     *
     * @see PinyinIME.ImeState
     */
    private PinyinIME.ImeState mImeState = PinyinIME.ImeState.STATE_IDLE;

    /**
     * The decoding information, include spelling(Pinyin) string, decoding result, etc.
     */
    private final DecodingInfo mDecInfo = new DecodingInfo();

    /**
     * For English input.
     */
    private EnglishInputProcessor mImEn;

    /**
     * Necessary environment configurations like screen size for this IME.
     */
    private Environment mEnvironment;

    /**
     * Used to switch input mode.
     */
    private InputModeSwitcher mInputModeSwitcher;

    /**
     * Soft keyboard container view to host real soft keyboard view.
     */
    private SkbContainer mSkbContainer;

    /**
     * View to show the composing string.
     */
    private LancooComposingView mComposingView;

    /**
     * View to show candidates list.
     */
    private LancooCandidateView mCandidatesContainer;

    /**
     * Connection used to bind the decoding service.
     */
    private PinyinDecoderServiceConnection mPinyinDecoderServiceConnection;

    /**
     * The on-screen movement gesture detector for soft keyboard.
     */
    private GestureDetector mGestureDetectorSkb;

    private Context mContext;

    public void onCreate(Context context) {
        mEnvironment = Environment.getInstance();

        mContext = context.getApplicationContext();
        startPinyinDecoderService(context);
        mImEn = new EnglishInputProcessor();

        mInputModeSwitcher = new InputModeSwitcher(this);
        OnGestureListener mGestureListenerSkb = new OnGestureListener(false);// Used to notify gestures from soft keyboard.
        mGestureDetectorSkb = new GestureDetector(context, mGestureListenerSkb);

        mEnvironment.onConfigurationChanged(getResources().getConfiguration(), context);
    }

    public void onStartInput() {
        EditorInfo editorInfo = new EditorInfo();
        //updateIcon(mInputModeSwitcher.requestInputWithHkb(editorInfo));
        mInputModeSwitcher.requestInputWithHkb(editorInfo);
        resetToIdleState();
    }

    public void onCreateInputView(LancooComposingView composingView, LancooCandidateView candidateView, SkbContainer skbContainer) {
        mComposingView = composingView;
        mCandidatesContainer = candidateView;

        mSkbContainer = skbContainer;
        mSkbContainer.setService(this);
        mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);
        mSkbContainer.setGestureDetector(mGestureDetectorSkb);

        mCandidatesContainer.initialize(new LancooCandidateViewListener() {
            @Override
            public void onClickChoice(String resultStr) {
                onChoiceTouched(resultStr);
            }

            @Override
            public void onDelete() {
                KeyEvent eUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
                        KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
                onKeyUp(KeyEvent.KEYCODE_DEL, eUp);
            }

            @Override
            public void onReset() {
                resetToIdleState();
            }
        });
    }

    public void onStartInputView() {
        EditorInfo editorInfo = new EditorInfo();
        //updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo));
        mInputModeSwitcher.requestInputWithSkb(editorInfo);

        resetToIdleState();
        mSkbContainer.updateInputMode();

        //setCandidatesViewShown(false);
    }

    public void onFinishInputView() {
        resetToIdleState();
    }

    public void onFinishInput() {
        resetToIdleState();
    }

    public void onDestroy(Context context) {
        if (mPinyinDecoderServiceConnection == null) return;
        context.unbindService(mPinyinDecoderServiceConnection);
    }

    private boolean startPinyinDecoderService(Context context) {
        if (null == mDecInfo.mIPinyinDecoderService) {
            Intent serviceIntent = new Intent();
            serviceIntent.setClass(context, PinyinDecoderService.class);

            if (null == mPinyinDecoderServiceConnection) {
                mPinyinDecoderServiceConnection = new PinyinDecoderServiceConnection();
            }

            // Bind service
            if (context.bindService(serviceIntent, mPinyinDecoderServiceConnection, Context.BIND_AUTO_CREATE)) {
                return true;
            } else {
                return false;
            }
        }
        return true;
    }

    /**
     * Connection used for binding to the Pinyin decoding service.
     */
    private class PinyinDecoderServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            mDecInfo.mIPinyinDecoderService = IPinyinDecoderService.Stub.asInterface(service);
        }

        public void onServiceDisconnected(ComponentName name) {
        }
    }

    Resources getResources() {
        return mContext.getResources();
    }

    void showOptionsMenu() {
        // DoNoThing
    }

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (processKey(event, 0 != event.getRepeatCount())) return true;
        return false;
    }

    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (processKey(event, true)) return true;
        return false;
    }

    private boolean processKey(KeyEvent event, boolean realAction) {
        if (PinyinIME.ImeState.STATE_BYPASS == mImeState) return false;

        int keyCode = event.getKeyCode();
        // SHIFT-SPACE is used to switch between Chinese and English when HKB is on.
        if (KeyEvent.KEYCODE_SPACE == keyCode && event.isShiftPressed()) {
            if (!realAction) return true;

            //updateIcon(mInputModeSwitcher.switchLanguageWithHkb());
            mInputModeSwitcher.switchLanguageWithHkb();
            resetToIdleState();

            /*int allMetaState = KeyEvent.META_ALT_ON
                    | KeyEvent.META_ALT_LEFT_ON
                    | KeyEvent.META_ALT_RIGHT_ON
                    | KeyEvent.META_SHIFT_ON
                    | KeyEvent.META_SHIFT_LEFT_ON
                    | KeyEvent.META_SHIFT_RIGHT_ON
                    | KeyEvent.META_SYM_ON;
            getCurrentInputConnection().clearMetaKeyStates(allMetaState);*/
            return true;
        }

        // If HKB is on to input English,
        // by-pass the key event so that default key listener will handle it.
        if (mInputModeSwitcher.isEnglishWithHkb()) {
            return false;
        }

        if (processFunctionKeys(keyCode, realAction)) {
            return true;
        }

        int keyChar = 0;
        if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
            keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';
        } else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
            keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';
        } else if (keyCode == KeyEvent.KEYCODE_COMMA) {
            keyChar = ',';
        } else if (keyCode == KeyEvent.KEYCODE_PERIOD) {
            keyChar = '.';
        } else if (keyCode == KeyEvent.KEYCODE_SPACE) {
            keyChar = ' ';
        } else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE) {
            keyChar = '\'';
        }

        if (mInputModeSwitcher.isEnglishWithSkb()) {
            //return mImEn.processKey(getCurrentInputConnection(), event, mInputModeSwitcher.isEnglishUpperCaseWithSkb(), realAction);
            return mImEn.processKey_lancoo(event, mInputModeSwitcher.isEnglishUpperCaseWithSkb(), realAction, new EnglishInputProcessor.OnInputStateCallback() {
                @Override
                public void deleteSurroundingText(int beforeLength, int afterLength) {
                    LancooPinyinIME.this.deleteSurroundingText(beforeLength, afterLength);
                }

                @Override
                public void commitResultText(String text) {
                    LancooPinyinIME.this.commitResultText(text);
                }
            });
        } else if (mInputModeSwitcher.isChineseText()) {
            if (mImeState == PinyinIME.ImeState.STATE_IDLE || mImeState == PinyinIME.ImeState.STATE_APP_COMPLETION) {
                mImeState = PinyinIME.ImeState.STATE_IDLE;
                return processStateIdle(keyChar, keyCode, event, realAction);
            } else if (mImeState == PinyinIME.ImeState.STATE_INPUT) {
                return processStateInput(keyChar, keyCode, event, realAction);
            } else if (mImeState == PinyinIME.ImeState.STATE_PREDICT) {
                return processStatePredict(keyChar, keyCode, event, realAction);
            } else if (mImeState == PinyinIME.ImeState.STATE_COMPOSING) {
                return processStateEditComposing(keyChar, keyCode, event, realAction);
            }
        } else {
            if (0 != keyChar && realAction) {
                commitResultText(String.valueOf((char) keyChar));
            }
        }

        return false;
    }

    private void resetToIdleState() {
        if (PinyinIME.ImeState.STATE_IDLE == mImeState) return;

        mImeState = PinyinIME.ImeState.STATE_IDLE;
        mDecInfo.reset();

        if (null != mComposingView) mComposingView.reset();
        //if (resetInlineText) commitResultText("");
        resetCandidateWindow();
    }

    private void resetCandidateWindow() {
        /*if (mEnvironment.needDebug()) {
            Log.d(TAG, "Candidates window is to be reset");
        }
        if (null == mCandidatesContainer) return;
        try {
            mFloatingWindowTimer.cancelShowing();
            mFloatingWindow.dismiss();
        } catch (Exception e) {
            Log.e(TAG, "Fail to show the PopupWindow.");
        }*/

        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.toggleCandidateMode(false);
        }

        mDecInfo.resetCandidates();

        /*if (null != mCandidatesContainer && mCandidatesContainer.isShown()) {
            showCandidateWindow(false);
        }*/
        showCandidateWindow(false);
    }

    // keyCode can be from both hard key or soft key.
    private boolean processFunctionKeys(int keyCode, boolean realAction) {
        // Back key is used to dismiss all popup UI in a soft keyboard.
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            /*if (isInputViewShown()) {
                if (mSkbContainer.handleBack(realAction)) return true;
            }*/
            if (mSkbContainer.isShown()) {
                if (mSkbContainer.handleBack(realAction)) return true;
            }
        }

        // Chinese related input is handle separately.
        if (mInputModeSwitcher.isChineseText()) {
            return false;
        }

        //if (null != mCandidatesContainer && mCandidatesContainer.isShown() && !mDecInfo.isCandidatesListEmpty()) {
        if (!mDecInfo.isCandidatesListEmpty()) {
            if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
                if (!realAction) return true;

                chooseCandidate(-1);
                return true;
            }

            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
                if (!realAction) return true;
                //mCandidatesContainer.activeCurseBackward();
                return true;
            }

            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                if (!realAction) return true;
                //mCandidatesContainer.activeCurseForward();
                return true;
            }

            if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                if (!realAction) return true;
                //mCandidatesContainer.pageBackward(false, true);
                return true;
            }

            if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                if (!realAction) return true;
                //mCandidatesContainer.pageForward(false, true);
                return true;
            }

            if (keyCode == KeyEvent.KEYCODE_DEL && PinyinIME.ImeState.STATE_PREDICT == mImeState) {
                if (!realAction) return true;
                resetToIdleState();
                return true;
            }
        } else {
            if (keyCode == KeyEvent.KEYCODE_DEL) {
                if (!realAction) return true;
                if (SIMULATE_KEY_DELETE) {
                    simulateKeyEventDownUp(keyCode);
                } else {
                    // getCurrentInputConnection().deleteSurroundingText(1, 0);
                    deleteSurroundingText(1, 0);
                }
                return true;
            }
            if (keyCode == KeyEvent.KEYCODE_ENTER) {
                if (!realAction) return true;
                sendKeyChar('\n');
                return true;
            }
            if (keyCode == KeyEvent.KEYCODE_SPACE) {
                if (!realAction) return true;
                sendKeyChar(' ');
                return true;
            }
        }

        return false;
    }

    private boolean processStateIdle(int keyChar, int keyCode, KeyEvent event, boolean realAction) {
        // In this status, when user presses keys in [a..z], the status will change to input state.
        if (keyChar >= 'a' && keyChar <= 'z' && !event.isAltPressed()) {
            if (!realAction) return true;
            mDecInfo.addSplChar((char) keyChar, true);
            chooseAndUpdate(-1);
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
            if (!realAction) return true;
            if (SIMULATE_KEY_DELETE) {
                simulateKeyEventDownUp(keyCode);
            } else {
                //getCurrentInputConnection().deleteSurroundingText(1, 0);
                deleteSurroundingText(1, 0);
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
            if (!realAction) return true;
            sendKeyChar('\n');
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
                || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
                || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
            return true;
        } else if (event.isAltPressed()) {
            char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
            if (0 != fullwidth_char) {
                if (realAction) {
                    String result = String.valueOf(fullwidth_char);
                    commitResultText(result);
                }
                return true;
            } else {
                if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
                    return true;
                }
            }
        } else if (keyChar != 0 && keyChar != '\t') {
            if (realAction) {
                if (keyChar == ',' || keyChar == '.') {
                    inputCommaPeriod("", keyChar, false, PinyinIME.ImeState.STATE_IDLE);
                } else {
                    if (0 != keyChar) {
                        String result = String.valueOf((char) keyChar);
                        commitResultText(result);
                    }
                }
            }
            return true;
        }
        return false;
    }

    private boolean processStateInput(int keyChar, int keyCode, KeyEvent event, boolean realAction) {
        // If ALT key is pressed, input alternative key.
        // But if the alternative key is quote key,
        // it will be used for input a splitter in Pinyin string.
        if (event.isAltPressed()) {
            if ('\'' != event.getUnicodeChar(event.getMetaState())) {
                if (realAction) {
                    char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
                    if (0 != fullwidth_char) {
                        //commitResultText(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos()) + String.valueOf(fullwidth_char));
                        commitResultText(mDecInfo.getCurrentFullSent(0) + fullwidth_char);
                        resetToIdleState();
                    }
                }
                return true;
            } else {
                keyChar = '\'';
            }
        }

        if (keyChar >= 'a' && keyChar <= 'z' || keyChar == '\''
                && !mDecInfo.charBeforeCursorIsSeparator()
                || keyCode == KeyEvent.KEYCODE_DEL) {
            if (!realAction) return true;
            return processSurfaceChange(keyChar, keyCode);
        } else if (keyChar == ',' || keyChar == '.') {
            if (!realAction) return true;
            //inputCommaPeriod(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos()), keyChar, true, PinyinIME.ImeState.STATE_IDLE);
            inputCommaPeriod(mDecInfo.getCurrentFullSent(0), keyChar, true, PinyinIME.ImeState.STATE_IDLE);
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
                || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
                || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            if (!realAction) return true;

            /*if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
                mCandidatesContainer.activeCurseBackward();
            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                mCandidatesContainer.activeCurseForward();
            } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                // If it has been the first page,
                // a up key will shift the state to edit composing string.
                if (!mCandidatesContainer.pageBackward(false, true)) {
                    mCandidatesContainer.enableActiveHighlight(false);
                    changeToStateComposing(true);
                    updateComposingText(true);
                }
            } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                mCandidatesContainer.pageForward(false, true);
            }*/
            return true;
        } else if (keyCode >= KeyEvent.KEYCODE_1 && keyCode <= KeyEvent.KEYCODE_9) {
            if (!realAction) return true;

            int activePos = keyCode - KeyEvent.KEYCODE_1;
            //int currentPage = mCandidatesContainer.getCurrentPage();
            int currentPage = 0;
            if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
                activePos = activePos + mDecInfo.getCurrentPageStart(currentPage);
                if (activePos >= 0) {
                    chooseAndUpdate(activePos);
                }
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
            if (!realAction) return true;
            if (mInputModeSwitcher.isEnterNoramlState()) {
                commitResultText(mDecInfo.getOrigianlSplStr().toString());
                resetToIdleState();
            } else {
                //commitResultText(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos()));
                commitResultText(mDecInfo.getCurrentFullSent(0));
                sendKeyChar('\n');
                resetToIdleState();
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_SPACE) {
            if (!realAction) return true;
            chooseCandidate(-1);
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (!realAction) return true;
            resetToIdleState();
            requestHideSelf();
            return true;
        }
        return false;
    }

    private boolean processStatePredict(int keyChar, int keyCode, KeyEvent event, boolean realAction) {
        if (!realAction) return true;

        // If ALT key is pressed, input alternative key.
        if (event.isAltPressed()) {
            char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
            if (0 != fullwidth_char) {
                //commitResultText(mDecInfo.getCandidate(mCandidatesContainer.getActiveCandiatePos()) + String.valueOf(fullwidth_char));
                commitResultText(mDecInfo.getCandidate(0) + fullwidth_char);
                resetToIdleState();
            }
            return true;
        }

        // In this status, when user presses keys in [a..z], the status will change to input state.
        if (keyChar >= 'a' && keyChar <= 'z') {
            changeToStateInput(true);
            mDecInfo.addSplChar((char) keyChar, true);
            chooseAndUpdate(-1);
        } else if (keyChar == ',' || keyChar == '.') {
            inputCommaPeriod("", keyChar, true, PinyinIME.ImeState.STATE_IDLE);
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
                || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
                || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            /*if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
                mCandidatesContainer.activeCurseBackward();
            }
            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                mCandidatesContainer.activeCurseForward();
            }
            if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                mCandidatesContainer.pageBackward(false, true);
            }
            if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                mCandidatesContainer.pageForward(false, true);
            }*/
        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
            resetToIdleState();
        } else if (keyCode == KeyEvent.KEYCODE_BACK) {
            resetToIdleState();
            requestHideSelf();
        } else if (keyCode >= KeyEvent.KEYCODE_1 && keyCode <= KeyEvent.KEYCODE_9) {
            int activePos = keyCode - KeyEvent.KEYCODE_1;
            //int currentPage = mCandidatesContainer.getCurrentPage();
            int currentPage = 0;
            if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
                activePos = activePos + mDecInfo.getCurrentPageStart(currentPage);
                if (activePos >= 0) {
                    chooseAndUpdate(activePos);
                }
            }
        } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
            sendKeyChar('\n');
            resetToIdleState();
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_SPACE) {
            chooseCandidate(-1);
        }

        return true;
    }

    private boolean processStateEditComposing(int keyChar, int keyCode, KeyEvent event, boolean realAction) {
        if (!realAction) return true;

        ComposingStatus cmpsvStatus = mComposingView.getComposingStatus();

        // If ALT key is pressed, input alternative key.
        // But if the alternative key is quote key, it will be used for input a splitter in Pinyin string.
        if (event.isAltPressed()) {
            if ('\'' != event.getUnicodeChar(event.getMetaState())) {
                char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
                if (0 != fullwidth_char) {
                    String retStr;
                    if (ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {
                        retStr = mDecInfo.getOrigianlSplStr().toString();
                    } else {
                        retStr = mDecInfo.getComposingStr();
                    }
                    commitResultText(retStr + fullwidth_char);
                    resetToIdleState();
                }
                return true;
            } else {
                keyChar = '\'';
            }
        }

        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
            if (!mDecInfo.selectionFinished()) {
                changeToStateInput(true);
            }
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            mComposingView.moveCursor(keyCode);
        } else if ((keyCode == KeyEvent.KEYCODE_ENTER && mInputModeSwitcher.isEnterNoramlState())
                || keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_SPACE) {
            if (ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {
                String str = mDecInfo.getOrigianlSplStr().toString();
                if (!tryInputRawUnicode(str)) {
                    commitResultText(str);
                }
            } else if (ComposingStatus.EDIT_PINYIN == cmpsvStatus) {
                String str = mDecInfo.getComposingStr();
                if (!tryInputRawUnicode(str)) {
                    commitResultText(str);
                }
            } else {
                commitResultText(mDecInfo.getComposingStr());
            }
            resetToIdleState();
        } else if (keyCode == KeyEvent.KEYCODE_ENTER && !mInputModeSwitcher.isEnterNoramlState()) {
            String retStr;
            if (!mDecInfo.isCandidatesListEmpty()) {
                //retStr = mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos());
                retStr = mDecInfo.getCurrentFullSent(0);
            } else {
                retStr = mDecInfo.getComposingStr();
            }
            commitResultText(retStr);
            sendKeyChar('\n');
            resetToIdleState();
        } else if (keyCode == KeyEvent.KEYCODE_BACK) {
            resetToIdleState();
            requestHideSelf();
            return true;
        } else {
            return processSurfaceChange(keyChar, keyCode);
        }

        return true;
    }

    private void inputCommaPeriod(String preEdit, int keyChar, boolean dismissCandWindow, PinyinIME.ImeState nextState) {
        if (keyChar == ',')
            preEdit += '\uff0c';
        else if (keyChar == '.')
            preEdit += '\u3002';
        else
            return;
        commitResultText(preEdit);
        if (dismissCandWindow) resetCandidateWindow();
        mImeState = nextState;
    }

    private boolean processSurfaceChange(int keyChar, int keyCode) {
        if (mDecInfo.isSplStrFull() && KeyEvent.KEYCODE_DEL != keyCode) {
            return true;
        }

        if ((keyChar >= 'a' && keyChar <= 'z')
                || (keyChar == '\'' && !mDecInfo.charBeforeCursorIsSeparator())
                || (((keyChar >= '0' && keyChar <= '9') || keyChar == ' ') && PinyinIME.ImeState.STATE_COMPOSING == mImeState)) {
            mDecInfo.addSplChar((char) keyChar, false);
            chooseAndUpdate(-1);
        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
            mDecInfo.prepareDeleteBeforeCursor();
            chooseAndUpdate(-1);
        }
        return true;
    }

    // If activeCandNo is less than 0,
    // get the current active candidate number from candidate view, otherwise use activeCandNo.
    private void chooseCandidate(int activeCandNo) {
        if (activeCandNo < 0) {
            // activeCandNo = mCandidatesContainer.getActiveCandiatePos();
            activeCandNo = 0;
        }
        if (activeCandNo >= 0) {
            chooseAndUpdate(activeCandNo);
        }
    }

    private void chooseAndUpdate(int candId) {
        if (!mInputModeSwitcher.isChineseText()) {
            String choice = mDecInfo.getCandidate(candId);
            if (null != choice) {
                commitResultText(choice);
            }
            resetToIdleState();
            return;
        }

        if (PinyinIME.ImeState.STATE_PREDICT != mImeState) {
            // Get result candidate list, if choice_id < 0, do a new decoding.
            // If choice_id >=0, select the candidate, and get the new candidate list.
            mDecInfo.chooseDecodingCandidate(candId);
        } else {
            // Choose a prediction item.
            mDecInfo.choosePredictChoice(candId);
        }

        if (mDecInfo.getComposingStr().length() > 0) {
            String resultStr;
            resultStr = mDecInfo.getComposingStrActivePart();

            // choiceId >= 0 means user finishes a choice selection.
            if (candId >= 0 && mDecInfo.canDoPrediction()) {
                commitResultText(resultStr);
                mImeState = PinyinIME.ImeState.STATE_PREDICT;
                if (null != mSkbContainer && mSkbContainer.isShown()) {
                    mSkbContainer.toggleCandidateMode(false);
                }
                // Try to get the prediction list.
                /*if (Settings.getPrediction()) {
                    InputConnection ic = getCurrentInputConnection();
                    if (null != ic) {
                        CharSequence cs = ic.getTextBeforeCursor(3, 0);
                        if (null != cs) {
                            mDecInfo.preparePredicts(cs);
                        }
                    }
                } else {
                    mDecInfo.resetCandidates();
                }*/

                if (mDecInfo.mCandidatesList.size() > 0) {
                    showCandidateWindow(false);
                } else {
                    resetToIdleState();
                }
            } else {
                if (PinyinIME.ImeState.STATE_IDLE == mImeState) {
                    if (mDecInfo.getSplStrDecodedLen() == 0) {
                        changeToStateComposing(true);
                    } else {
                        changeToStateInput(true);
                    }
                } else {
                    if (mDecInfo.selectionFinished()) {
                        changeToStateComposing(true);
                    }
                }
                showCandidateWindow(true);
            }
        } else {
            resetToIdleState();
        }
    }

    private void onChoiceTouched(String resultStr) {
        if (mImeState == PinyinIME.ImeState.STATE_COMPOSING) {
            changeToStateInput(true);
        } else if (mImeState == PinyinIME.ImeState.STATE_INPUT || mImeState == PinyinIME.ImeState.STATE_PREDICT) {
            //chooseCandidate(activeCandNo);
            chooseAndUpdateForChinese(resultStr);
        } else if (mImeState == PinyinIME.ImeState.STATE_APP_COMPLETION) {
            /*if (null != mDecInfo.mAppCompletions && activeCandNo >= 0 && activeCandNo < mDecInfo.mAppCompletions.length) {
                CompletionInfo ci = mDecInfo.mAppCompletions[activeCandNo];
                if (null != ci) {
                    InputConnection ic = getCurrentInputConnection();
                    ic.commitCompletion(ci);
                }
            }*/
            resetToIdleState();
        }
    }

    private void chooseAndUpdateForChinese(String resultStr) {
        commitResultText(resultStr);
        mImeState = PinyinIME.ImeState.STATE_PREDICT;
        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.toggleCandidateMode(false);
        }

        showCandidateWindow(false);
    }

    private void requestHideSelf() {
        /*if (mEnvironment.needDebug()) {
            Log.d(TAG, "DimissSoftInput.");
        }*/
        dismissCandidateWindow();
        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.dismissPopups();
        }
        //super.requestHideSelf(flags);
    }

    private void showCandidateWindow(boolean showComposingView) {
        /*if (mEnvironment.needDebug()) {
            Log.d(TAG, "Candidates window is shown. Parent = " + mCandidatesContainer);
        }

        setCandidatesViewShown(true);*/

        if (null != mSkbContainer) mSkbContainer.requestLayout();

        /*if (null == mCandidatesContainer) {
            resetToIdleState();
            return;
        }*/

        updateComposingText(showComposingView);
        /*mCandidatesContainer.showCandidates(mDecInfo, PinyinIME.ImeState.STATE_COMPOSING != mImeState);
        mFloatingWindowTimer.postShowFloatingWindow();*/
        if (showComposingView) {
            mCandidatesContainer.showCandidates(mDecInfo);
        } else {
            mCandidatesContainer.hideCandidates();
        }
    }

    private void dismissCandidateWindow() {
        /*if (mEnvironment.needDebug()) {
            Log.d(TAG, "Candidates window is to be dismissed");
        }
        if (null == mCandidatesContainer) return;
        try {
            mFloatingWindowTimer.cancelShowing();
            mFloatingWindow.dismiss();
        } catch (Exception e) {
            Log.e(TAG, "Fail to show the PopupWindow.");
        }
        setCandidatesViewShown(false);*/

        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.toggleCandidateMode(false);
        }
    }

    private void changeToStateComposing(boolean updateUi) {
        mImeState = PinyinIME.ImeState.STATE_COMPOSING;
        if (!updateUi) return;

        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.toggleCandidateMode(true);
        }
    }

    private void changeToStateInput(boolean updateUi) {
        mImeState = PinyinIME.ImeState.STATE_INPUT;
        if (!updateUi) return;

        if (null != mSkbContainer && mSkbContainer.isShown()) {
            mSkbContainer.toggleCandidateMode(true);
        }
        showCandidateWindow(true);
    }

    private boolean tryInputRawUnicode(String str) {
        if (str.length() > 7) {
            if (str.substring(0, 7).compareTo("unicode") == 0) {
                try {
                    String digitStr = str.substring(7);
                    int startPos = 0;
                    int radix = 10;
                    if (digitStr.length() > 2 && digitStr.charAt(0) == '0' && digitStr.charAt(1) == 'x') {
                        startPos = 2;
                        radix = 16;
                    }
                    digitStr = digitStr.substring(startPos);
                    int unicode = Integer.parseInt(digitStr, radix);
                    if (unicode > 0) {
                        char low = (char) (unicode & 0x0000ffff);
                        char high = (char) ((unicode & 0xffff0000) >> 16);
                        commitResultText(String.valueOf(low));
                        if (0 != high) {
                            commitResultText(String.valueOf(high));
                        }
                    }
                    return true;
                } catch (NumberFormatException e) {
                    return false;
                }
            } else if (str.substring(str.length() - 7, str.length()).compareTo("unicode") == 0) {
                String resultStr = "";
                for (int pos = 0; pos < str.length() - 7; pos++) {
                    if (pos > 0) {
                        resultStr += " ";
                    }

                    resultStr += "0x" + Integer.toHexString(str.charAt(pos));
                }
                commitResultText(resultStr);
                return true;
            }
        }
        return false;
    }

    private void deleteSurroundingText(int beforeLength, int afterLength) {// ？？
        Log.e("My", "deleteSurroundingText===========>>>" + beforeLength + ", " + afterLength);
    }

    // 模拟按键
    private void simulateKeyEventDownUp(int keyCode) {// ？？
        /*InputConnection ic = getCurrentInputConnection();
        if (null == ic) return;

        ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
        ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));*/
        Log.e("My", "simulateKeyEventDownUp===========>>>" + keyCode);
    }

    public void sendKeyChar(char charCode) {// ？？
        Log.e("My", "sendKeyChar===========>>>" + charCode);
    }

    private void commitResultText(String resultText) {// ？？
        /*InputConnection ic = getCurrentInputConnection();
        if (null != ic) ic.commitText(resultText, 1);*/
        if (null != mComposingView) {
            mComposingView.setVisibility(View.GONE);
            mComposingView.invalidate();
        }
        Log.e("My", "commitResultText===========>>>" + resultText);
    }

    private void updateComposingText(boolean visible) {
        if (!visible) {
            mComposingView.setVisibility(View.GONE);
        } else {
            mComposingView.setDecodingInfo(mDecInfo, mImeState);
            mComposingView.setVisibility(View.VISIBLE);
        }
        mComposingView.invalidate();
    }

    public void responseSoftKeyEvent(SoftKey sKey) {
        if (null == sKey) return;

        /*InputConnection ic = getCurrentInputConnection();
        if (ic == null) return;*/

        int keyCode = sKey.getKeyCode();
        // Process some general keys, including KEYCODE_DEL, KEYCODE_SPACE, KEYCODE_ENTER and KEYCODE_DPAD_CENTER.
        if (sKey.isKeyCodeKey()) {
            if (processFunctionKeys(keyCode, true)) return;
        }

        if (sKey.isUserDefKey()) {
            //updateIcon(mInputModeSwitcher.switchModeForUserKey(keyCode));
            mInputModeSwitcher.switchModeForUserKey(keyCode);
            resetToIdleState();
            mSkbContainer.updateInputMode();
        } else {
            if (sKey.isKeyCodeKey()) {
                KeyEvent eDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
                        keyCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
                KeyEvent eUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
                        keyCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);

                onKeyDown(keyCode, eDown);
                onKeyUp(keyCode, eUp);
            } else if (sKey.isUniStrKey()) {
                boolean kUsed = false;
                String keyLabel = sKey.getKeyLabel();
                if (mInputModeSwitcher.isChineseTextWithSkb()
                        && (PinyinIME.ImeState.STATE_INPUT == mImeState || PinyinIME.ImeState.STATE_COMPOSING == mImeState)) {
                    if (mDecInfo.length() > 0 && keyLabel.length() == 1 && keyLabel.charAt(0) == '\'') {
                        processSurfaceChange('\'', 0);
                        kUsed = true;
                    }
                }
                if (!kUsed) {
                    if (PinyinIME.ImeState.STATE_INPUT == mImeState) {
                        //commitResultText(mDecInfo.getCurrentFullSent(mCandidatesContainer.getActiveCandiatePos()));
                        commitResultText(mDecInfo.getCurrentFullSent(0));
                    } else if (PinyinIME.ImeState.STATE_COMPOSING == mImeState) {
                        commitResultText(mDecInfo.getComposingStr());
                    }
                    commitResultText(keyLabel);
                    resetToIdleState();
                }
            }

            // If the current soft keyboard is not sticky,
            // IME needs to go back to the previous soft keyboard automatically.
            if (!mSkbContainer.isCurrentSkbSticky()) {
                //updateIcon(mInputModeSwitcher.requestBackToPreviousSkb());
                mInputModeSwitcher.requestBackToPreviousSkb();
                resetToIdleState();
                mSkbContainer.updateInputMode();
            }
        }
    }

    public class DecodingInfo {
        /**
         * Maximum length of the Pinyin string
         */
        private static final int PY_STRING_MAX = 28;

        /**
         * Maximum number of candidates to display in one page.
         */
        private static final int MAX_PAGE_SIZE_DISPLAY = 10;

        /**
         * Spelling (Pinyin) string.
         */
        private StringBuffer mSurface;

        /**
         * Byte buffer used as the Pinyin string parameter for native function call.
         */
        private byte mPyBuf[];

        /**
         * The length of surface string successfully decoded by engine.
         */
        private int mSurfaceDecodedLen;

        /**
         * Composing string.
         */
        private String mComposingStr;

        /**
         * Length of the active composing string.
         */
        private int mActiveCmpsLen;

        /**
         * Composing string for display, it is copied from mComposingStr, and add spaces between spellings.
         **/
        private String mComposingStrDisplay;

        /**
         * Length of the active composing string for display.
         */
        private int mActiveCmpsDisplayLen;

        /**
         * The first full sentence choice.
         */
        private String mFullSent;

        /**
         * Number of characters which have been fixed.
         */
        private int mFixedLen;

        /**
         * If this flag is true, selection is finished.
         */
        private boolean mFinishSelection;

        /**
         * The starting position for each spelling.
         * The first one is the number of the real starting position elements.
         */
        private int mSplStart[];

        /**
         * Editing cursor in mSurface.
         */
        private int mCursorPos;

        /**
         * Remote Pinyin-to-Hanzi decoding engine service.
         */
        private IPinyinDecoderService mIPinyinDecoderService;

        /**
         * The complication information suggested by application.
         */
        private CompletionInfo[] mAppCompletions;

        /**
         * The total number of choices for display. The list may only contains the first part.
         * If user tries to navigate to next page which is not in the result list, we need to get these items.
         **/
        public int mTotalChoicesNum;

        /**
         * Candidate list. The first one is the full-sentence candidate.
         */
        public List<String> mCandidatesList = new Vector<String>();

        /**
         * Element i stores the starting position of page i.
         */
        public Vector<Integer> mPageStart = new Vector<Integer>();

        /**
         * Element i stores the number of characters to page i.
         */
        public Vector<Integer> mCnToPage = new Vector<Integer>();

        /**
         * The position to delete in Pinyin string.
         * If it is less than 0, IME will do an incremental search, otherwise IME will do a deletion operation.
         * if {@link #mIsPosInSpl} is true, IME will delete the whole string for mPosDelSpl-th spelling,
         * otherwise it will only delete mPosDelSpl-th character in the Pinyin string.
         */
        public int mPosDelSpl = -1;

        /**
         * If {@link #mPosDelSpl} is big than or equal to 0,
         * this member is used to indicate that whether the postion is counted in spelling id or character.
         */
        public boolean mIsPosInSpl;

        public DecodingInfo() {
            mSurface = new StringBuffer();
            mSurfaceDecodedLen = 0;
        }

        public void reset() {
            mSurface.delete(0, mSurface.length());
            mSurfaceDecodedLen = 0;
            mCursorPos = 0;
            mFullSent = "";
            mFixedLen = 0;
            mFinishSelection = false;
            mComposingStr = "";
            mComposingStrDisplay = "";
            mActiveCmpsLen = 0;
            mActiveCmpsDisplayLen = 0;

            resetCandidates();
        }

        public boolean isCandidatesListEmpty() {
            return mCandidatesList.size() == 0;
        }

        public boolean isSplStrFull() {
            if (mSurface.length() >= PY_STRING_MAX - 1) return true;
            return false;
        }

        public void addSplChar(char ch, boolean reset) {
            if (reset) {
                mSurface.delete(0, mSurface.length());
                mSurfaceDecodedLen = 0;
                mCursorPos = 0;
                try {
                    mIPinyinDecoderService.imResetSearch();
                } catch (RemoteException e) {
                }
            }
            mSurface.insert(mCursorPos, ch);
            mCursorPos++;
        }

        // Prepare to delete before cursor.
        // We may delete a spelling char if the cursor is in the range of unfixed part,
        // delete a whole spelling if the cursor in inside the range of the fixed part.
        // This function only marks the position used to delete.
        public void prepareDeleteBeforeCursor() {
            if (mCursorPos > 0) {
                int pos;
                for (pos = 0; pos < mFixedLen; pos++) {
                    if (mSplStart[pos + 2] >= mCursorPos && mSplStart[pos + 1] < mCursorPos) {
                        mPosDelSpl = pos;
                        mCursorPos = mSplStart[pos + 1];
                        mIsPosInSpl = true;
                        break;
                    }
                }
                if (mPosDelSpl < 0) {
                    mPosDelSpl = mCursorPos - 1;
                    mCursorPos--;
                    mIsPosInSpl = false;
                }
            }
        }

        public int length() {
            return mSurface.length();
        }

        public char charAt(int index) {
            return mSurface.charAt(index);
        }

        public StringBuffer getOrigianlSplStr() {
            return mSurface;
        }

        public int getSplStrDecodedLen() {
            return mSurfaceDecodedLen;
        }

        public int[] getSplStart() {
            return mSplStart;
        }

        public String getComposingStr() {
            return mComposingStr;
        }

        public String getComposingStrActivePart() {
            assert (mActiveCmpsLen <= mComposingStr.length());
            return mComposingStr.substring(0, mActiveCmpsLen);
        }

        public int getActiveCmpsLen() {
            return mActiveCmpsLen;
        }

        public String getComposingStrForDisplay() {
            return mComposingStrDisplay;
        }

        public int getActiveCmpsDisplayLen() {
            return mActiveCmpsDisplayLen;
        }

        public String getFullSent() {
            return mFullSent;
        }

        public String getCurrentFullSent(int activeCandPos) {
            try {
                String retStr = mFullSent.substring(0, mFixedLen);
                retStr += mCandidatesList.get(activeCandPos);
                return retStr;
            } catch (Exception e) {
                return "";
            }
        }

        public void resetCandidates() {
            mCandidatesList.clear();
            mTotalChoicesNum = 0;

            mPageStart.clear();
            mPageStart.add(0);
            mCnToPage.clear();
            mCnToPage.add(0);
        }

        public boolean candidatesFromApp() {
            return PinyinIME.ImeState.STATE_APP_COMPLETION == mImeState;
        }

        public boolean canDoPrediction() {
            return mComposingStr.length() == mFixedLen;
        }

        public boolean selectionFinished() {
            return mFinishSelection;
        }

        // After the user chooses a candidate,
        // input method will do a re-decoding and give the new candidate list.
        // If candidate id is less than 0, means user is inputting Pinyin, not selecting any choice.
        private void chooseDecodingCandidate(int candId) {
            if (mImeState != PinyinIME.ImeState.STATE_PREDICT) {
                resetCandidates();
                int totalChoicesNum = 0;
                try {
                    if (candId < 0) {
                        if (length() == 0) {
                            totalChoicesNum = 0;
                        } else {
                            if (mPyBuf == null)
                                mPyBuf = new byte[PY_STRING_MAX];
                            for (int i = 0; i < length(); i++)
                                mPyBuf[i] = (byte) charAt(i);
                            mPyBuf[length()] = 0;

                            if (mPosDelSpl < 0) {
                                totalChoicesNum = mIPinyinDecoderService.imSearch(mPyBuf, length());
                            } else {
                                boolean clear_fixed_this_step = true;
                                if (PinyinIME.ImeState.STATE_COMPOSING == mImeState) {
                                    clear_fixed_this_step = false;
                                }
                                totalChoicesNum = mIPinyinDecoderService.imDelSearch(mPosDelSpl, mIsPosInSpl, clear_fixed_this_step);
                                mPosDelSpl = -1;
                            }
                        }
                    } else {
                        totalChoicesNum = mIPinyinDecoderService.imChoose(candId);
                    }
                } catch (RemoteException e) {
                }
                updateDecInfoForSearch(totalChoicesNum);
            }
        }

        private void updateDecInfoForSearch(int totalChoicesNum) {
            mTotalChoicesNum = totalChoicesNum;
            if (mTotalChoicesNum < 0) {
                mTotalChoicesNum = 0;
                return;
            }

            try {
                String pyStr;

                mSplStart = mIPinyinDecoderService.imGetSplStart();
                pyStr = mIPinyinDecoderService.imGetPyStr(false);
                mSurfaceDecodedLen = mIPinyinDecoderService.imGetPyStrLen(true);
                assert (mSurfaceDecodedLen <= pyStr.length());

                mFullSent = mIPinyinDecoderService.imGetChoice(0);
                mFixedLen = mIPinyinDecoderService.imGetFixedLen();

                // Update the surface string to the one kept by engine.
                mSurface.replace(0, mSurface.length(), pyStr);

                if (mCursorPos > mSurface.length())
                    mCursorPos = mSurface.length();
                mComposingStr = mFullSent.substring(0, mFixedLen) + mSurface.substring(mSplStart[mFixedLen + 1]);

                mActiveCmpsLen = mComposingStr.length();
                if (mSurfaceDecodedLen > 0) {
                    mActiveCmpsLen = mActiveCmpsLen - (mSurface.length() - mSurfaceDecodedLen);
                }

                // Prepare the display string.
                if (0 == mSurfaceDecodedLen) {
                    mComposingStrDisplay = mComposingStr;
                    mActiveCmpsDisplayLen = mComposingStr.length();
                } else {
                    mComposingStrDisplay = mFullSent.substring(0, mFixedLen);
                    for (int pos = mFixedLen + 1; pos < mSplStart.length - 1; pos++) {
                        mComposingStrDisplay += mSurface.substring(mSplStart[pos], mSplStart[pos + 1]);
                        if (mSplStart[pos + 1] < mSurfaceDecodedLen) {
                            mComposingStrDisplay += " ";
                        }
                    }
                    mActiveCmpsDisplayLen = mComposingStrDisplay.length();
                    if (mSurfaceDecodedLen < mSurface.length()) {
                        mComposingStrDisplay += mSurface.substring(mSurfaceDecodedLen);
                    }
                }

                if (mSplStart.length == mFixedLen + 2) {
                    mFinishSelection = true;
                } else {
                    mFinishSelection = false;
                }
            } catch (RemoteException e) {
                Log.w(TAG, "PinyinDecoderService died", e);
            } catch (Exception e) {
                mTotalChoicesNum = 0;
                mComposingStr = "";
            }
            // Prepare page 0.
            if (!mFinishSelection) {
                preparePage(0);
            }
        }

        private void choosePredictChoice(int choiceId) {
            if (PinyinIME.ImeState.STATE_PREDICT != mImeState || choiceId < 0 || choiceId >= mTotalChoicesNum) {
                return;
            }

            String tmp = mCandidatesList.get(choiceId);

            resetCandidates();

            mCandidatesList.add(tmp);
            mTotalChoicesNum = 1;

            mSurface.replace(0, mSurface.length(), "");
            mCursorPos = 0;
            mFullSent = tmp;
            mFixedLen = tmp.length();
            mComposingStr = mFullSent;
            mActiveCmpsLen = mFixedLen;

            mFinishSelection = true;
        }

        public String getCandidate(int candId) {
            // Only loaded items can be gotten, so we use mCandidatesList.size() instead mTotalChoiceNum.
            if (candId < 0 || candId > mCandidatesList.size()) {
                return null;
            }
            return mCandidatesList.get(candId);
        }

        private void getCandiagtesForCache() {
            int fetchStart = mCandidatesList.size();
            int fetchSize = mTotalChoicesNum - fetchStart;
            if (fetchSize > MAX_PAGE_SIZE_DISPLAY) {
                fetchSize = MAX_PAGE_SIZE_DISPLAY;
            }
            try {
                List<String> newList = null;
                if (PinyinIME.ImeState.STATE_INPUT == mImeState ||
                        PinyinIME.ImeState.STATE_IDLE == mImeState ||
                        PinyinIME.ImeState.STATE_COMPOSING == mImeState) {
                    newList = mIPinyinDecoderService.imGetChoiceList(fetchStart, fetchSize, mFixedLen);
                } else if (PinyinIME.ImeState.STATE_PREDICT == mImeState) {
                    newList = mIPinyinDecoderService.imGetPredictList(fetchStart, fetchSize);
                } else if (PinyinIME.ImeState.STATE_APP_COMPLETION == mImeState) {
                    newList = new ArrayList<String>();
                    if (null != mAppCompletions) {
                        for (int pos = fetchStart; pos < fetchSize; pos++) {
                            CompletionInfo ci = mAppCompletions[pos];
                            if (null != ci) {
                                CharSequence s = ci.getText();
                                if (null != s) newList.add(s.toString());
                            }
                        }
                    }
                }
                mCandidatesList.addAll(newList);
            } catch (RemoteException e) {
                Log.w(TAG, "PinyinDecoderService died", e);
            }
        }

        public boolean pageReady(int pageNo) {
            // If the page number is less than 0, return false
            if (pageNo < 0) return false;

            // Page pageNo's ending information is not ready.
            if (mPageStart.size() <= pageNo + 1) {
                return false;
            }

            return true;
        }

        public boolean preparePage(int pageNo) {
            // If the page number is less than 0, return false
            if (pageNo < 0) return false;

            // Make sure the starting information for page pageNo is ready.
            if (mPageStart.size() <= pageNo) {
                return false;
            }

            // Page pageNo's ending information is also ready.
            if (mPageStart.size() > pageNo + 1) {
                return true;
            }

            // If cached items is enough for page pageNo.
            if (mCandidatesList.size() - mPageStart.elementAt(pageNo) >= MAX_PAGE_SIZE_DISPLAY) {
                return true;
            }

            // Try to get more items from engine
            getCandiagtesForCache();

            // Try to find if there are available new items to display.
            // If no new item, return false;
            if (mPageStart.elementAt(pageNo) >= mCandidatesList.size()) {
                return false;
            }

            // If there are new items, return true;
            return true;
        }

        public void preparePredicts(CharSequence history) {
            if (null == history) return;

            resetCandidates();

            if (Settings.getPrediction()) {
                String preEdit = history.toString();
                if (null != preEdit) {
                    try {
                        mTotalChoicesNum = mIPinyinDecoderService.imGetPredictsNum(preEdit);
                    } catch (RemoteException e) {
                        return;
                    }
                }
            }

            preparePage(0);
            mFinishSelection = false;
        }

        private void prepareAppCompletions(CompletionInfo completions[]) {
            resetCandidates();
            mAppCompletions = completions;
            mTotalChoicesNum = completions.length;
            preparePage(0);
            mFinishSelection = false;
        }

        public int getCurrentPageSize(int currentPage) {
            if (mPageStart.size() <= currentPage + 1) return 0;
            return mPageStart.elementAt(currentPage + 1) - mPageStart.elementAt(currentPage);
        }

        public int getCurrentPageStart(int currentPage) {
            if (mPageStart.size() < currentPage + 1) return mTotalChoicesNum;
            return mPageStart.elementAt(currentPage);
        }

        public boolean pageForwardable(int currentPage) {
            if (mPageStart.size() <= currentPage + 1) return false;
            if (mPageStart.elementAt(currentPage + 1) >= mTotalChoicesNum) {
                return false;
            }
            return true;
        }

        public boolean pageBackwardable(int currentPage) {
            if (currentPage > 0) return true;
            return false;
        }

        public boolean charBeforeCursorIsSeparator() {
            int len = mSurface.length();
            if (mCursorPos > len) return false;
            if (mCursorPos > 0 && mSurface.charAt(mCursorPos - 1) == '\'') {
                return true;
            }
            return false;
        }

        public int getCursorPos() {
            return mCursorPos;
        }

        public int getCursorPosInCmps() {
            int cursorPos = mCursorPos;
            int fixedLen = 0;

            for (int hzPos = 0; hzPos < mFixedLen; hzPos++) {
                if (mCursorPos >= mSplStart[hzPos + 2]) {
                    cursorPos -= mSplStart[hzPos + 2] - mSplStart[hzPos + 1];
                    cursorPos += 1;
                }
            }
            return cursorPos;
        }

        public int getCursorPosInCmpsDisplay() {
            int cursorPos = getCursorPosInCmps();
            // +2 is because: one for mSplStart[0],
            // which is used for other purpose(The length of the segmentation string),
            // and another for the first spelling which does not need a space before it.
            for (int pos = mFixedLen + 2; pos < mSplStart.length - 1; pos++) {
                if (mCursorPos <= mSplStart[pos]) {
                    break;
                } else {
                    cursorPos++;
                }
            }
            return cursorPos;
        }

        public void moveCursorToEdge(boolean left) {
            if (left)
                mCursorPos = 0;
            else
                mCursorPos = mSurface.length();
        }

        // Move cursor. If offset is 0,
        // this function can be used to adjust the cursor into the bounds of the string.
        public void moveCursor(int offset) {
            if (offset > 1 || offset < -1) return;

            if (offset != 0) {
                int hzPos = 0;
                for (hzPos = 0; hzPos <= mFixedLen; hzPos++) {
                    if (mCursorPos == mSplStart[hzPos + 1]) {
                        if (offset < 0) {
                            if (hzPos > 0) {
                                offset = mSplStart[hzPos] - mSplStart[hzPos + 1];
                            }
                        } else {
                            if (hzPos < mFixedLen) {
                                offset = mSplStart[hzPos + 2] - mSplStart[hzPos + 1];
                            }
                        }
                        break;
                    }
                }
            }
            mCursorPos += offset;
            if (mCursorPos < 0) {
                mCursorPos = 0;
            } else if (mCursorPos > mSurface.length()) {
                mCursorPos = mSurface.length();
            }
        }

        public int getSplNum() {
            return mSplStart[0];
        }

        public int getFixedLen() {
            return mFixedLen;
        }
    }

    public class OnGestureListener extends GestureDetector.SimpleOnGestureListener {
        /**
         * When user presses and drags, the minimum x-distance to make a response to the drag event.
         */
        private static final int MIN_X_FOR_DRAG = 60;

        /**
         * When user presses and drags, the minimum y-distance to make a response to the drag event.
         */
        private static final int MIN_Y_FOR_DRAG = 40;

        /**
         * Velocity threshold for a screen-move gesture. If the minimum x-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_X1 = 0.3f;

        /**
         * Velocity threshold for a screen-move gesture. If the maximum x-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_X2 = 0.7f;

        /**
         * Velocity threshold for a screen-move gesture. If the minimum y-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_Y1 = 0.2f;

        /**
         * Velocity threshold for a screen-move gesture. If the maximum y-velocity is less than it, no gesture.
         */
        static private final float VELOCITY_THRESHOLD_Y2 = 0.45f;

        /**
         * If it false, we will not response detected gestures.
         */
        private boolean mReponseGestures;

        /**
         * The minimum X velocity observed in the gesture.
         */
        private float mMinVelocityX = Float.MAX_VALUE;

        /**
         * The minimum Y velocity observed in the gesture.
         */
        private float mMinVelocityY = Float.MAX_VALUE;

        /**
         * The first down time for the series of touch events for an action.
         */
        private long mTimeDown;

        /**
         * The last time when onScroll() is called.
         */
        private long mTimeLastOnScroll;

        /**
         * This flag used to indicate that this gesture is not a gesture.
         */
        private boolean mNotGesture;

        /**
         * This flag used to indicate that this gesture has been recognized.
         */
        private boolean mGestureRecognized;

        public OnGestureListener(boolean reponseGestures) {
            mReponseGestures = reponseGestures;
        }

        @Override
        public boolean onDown(MotionEvent e) {
            mMinVelocityX = Integer.MAX_VALUE;
            mMinVelocityY = Integer.MAX_VALUE;
            mTimeDown = e.getEventTime();
            mTimeLastOnScroll = mTimeDown;
            mNotGesture = false;
            mGestureRecognized = false;
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (mNotGesture) return false;
            if (mGestureRecognized) return true;

            if (Math.abs(e1.getX() - e2.getX()) < MIN_X_FOR_DRAG && Math.abs(e1.getY() - e2.getY()) < MIN_Y_FOR_DRAG)
                return false;

            long timeNow = e2.getEventTime();
            long spanTotal = timeNow - mTimeDown;
            long spanThis = timeNow - mTimeLastOnScroll;
            if (0 == spanTotal) spanTotal = 1;
            if (0 == spanThis) spanThis = 1;

            float vXTotal = (e2.getX() - e1.getX()) / spanTotal;
            float vYTotal = (e2.getY() - e1.getY()) / spanTotal;

            // The distances are from the current point to the previous one.
            float vXThis = -distanceX / spanThis;
            float vYThis = -distanceY / spanThis;

            float kX = vXTotal * vXThis;
            float kY = vYTotal * vYThis;
            float k1 = kX + kY;
            float k2 = Math.abs(kX) + Math.abs(kY);

            if (k1 / k2 < 0.8) {
                mNotGesture = true;
                return false;
            }
            float absVXTotal = Math.abs(vXTotal);
            float absVYTotal = Math.abs(vYTotal);
            if (absVXTotal < mMinVelocityX) {
                mMinVelocityX = absVXTotal;
            }
            if (absVYTotal < mMinVelocityY) {
                mMinVelocityY = absVYTotal;
            }

            if (mMinVelocityX < VELOCITY_THRESHOLD_X1 && mMinVelocityY < VELOCITY_THRESHOLD_Y1) {
                mNotGesture = true;
                return false;
            }

            if (vXTotal > VELOCITY_THRESHOLD_X2
                    && absVYTotal < VELOCITY_THRESHOLD_Y2) {
                if (mReponseGestures) onDirectionGesture(Gravity.RIGHT);
                mGestureRecognized = true;
            } else if (vXTotal < -VELOCITY_THRESHOLD_X2
                    && absVYTotal < VELOCITY_THRESHOLD_Y2) {
                if (mReponseGestures) onDirectionGesture(Gravity.LEFT);
                mGestureRecognized = true;
            } else if (vYTotal > VELOCITY_THRESHOLD_Y2
                    && absVXTotal < VELOCITY_THRESHOLD_X2) {
                if (mReponseGestures) onDirectionGesture(Gravity.BOTTOM);
                mGestureRecognized = true;
            } else if (vYTotal < -VELOCITY_THRESHOLD_Y2
                    && absVXTotal < VELOCITY_THRESHOLD_X2) {
                if (mReponseGestures) onDirectionGesture(Gravity.TOP);
                mGestureRecognized = true;
            }

            mTimeLastOnScroll = timeNow;
            return mGestureRecognized;
        }

        @Override
        public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
            return mGestureRecognized;
        }

        public void onDirectionGesture(int gravity) {
            if (Gravity.NO_GRAVITY == gravity) {
                return;
            }

            if (Gravity.LEFT == gravity || Gravity.RIGHT == gravity) {
                /*if (mCandidatesContainer.isShown()) {
                    if (Gravity.LEFT == gravity) {
                        mCandidatesContainer.pageForward(true, true);
                    } else {
                        mCandidatesContainer.pageBackward(true, true);
                    }
                }*/
            }
        }
    }
}